<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
    />
    <meta name="description" content="For debugging voxels." />
    <meta name="cesium-sandcastle-labels" content="Development" />
    <title>Cesium Demo</title>
    <script type="text/javascript" src="../Sandcastle-header.js"></script>
    <script
      type="text/javascript"
      src="../../../Build/CesiumUnminified/Cesium.js"
      nomodule
    ></script>
    <script type="module" src="../load-cesium-es6.js"></script>
  </head>
  <body
    class="sandcastle-loading"
    data-sandcastle-bucket="bucket-requirejs.html"
  >
    <style>
      @import url(../templates/bucket.css);
    </style>
    <div id="cesiumContainer" class="fullSize"></div>
    <div id="loadingOverlay"><h1>Loading...</h1></div>
    <div id="toolbar"></div>
    <script id="cesium_sandcastle_script">
      window.startup = async function (Cesium) {
        "use strict";
        //Sandcastle_Begin
        const viewer = new Cesium.Viewer("cesiumContainer", {
          baseLayer: Cesium.ImageryLayer.fromProviderAsync(
            Cesium.TileMapServiceImageryProvider.fromUrl(
              Cesium.buildModuleUrl("Assets/Textures/NaturalEarthII")
            )
          ),
          baseLayerPicker: false,
          geocoder: false,
        });

        viewer.extend(Cesium.viewerVoxelInspectorMixin);
        viewer.scene.debugShowFramesPerSecond = true;

        function getMinBounds(shape) {
          if (shape === Cesium.VoxelShapeType.ELLIPSOID) {
            const minBounds = Cesium.Cartesian3.clone(
              Cesium.VoxelShapeType.getMinBounds(shape)
            );
            minBounds.z = 0.0;
            return minBounds;
          }

          return undefined;
        }

        function getMaxBounds(shape) {
          if (shape === Cesium.VoxelShapeType.ELLIPSOID) {
            const maxBounds = Cesium.Cartesian3.clone(
              Cesium.VoxelShapeType.getMaxBounds(shape)
            );
            maxBounds.z = 1000000.0;
            return maxBounds;
          }

          return undefined;
        }

        function ProceduralSingleTileVoxelProvider(shape) {
          this.shape = shape;
          this.minBounds = getMinBounds(shape);
          this.maxBounds = getMaxBounds(shape);
          this.dimensions = new Cesium.Cartesian3(8, 8, 8);
          this.names = ["color"];
          this.types = [Cesium.MetadataType.VEC4];
          this.componentTypes = [Cesium.MetadataComponentType.FLOAT32];
        }

        const scratchColor = new Cesium.Color();

        ProceduralSingleTileVoxelProvider.prototype.requestData = function (
          options
        ) {
          const tileLevel = options.tileLevel;
          const tileX = options.tileX;
          const tileY = options.tileY;
          const tileZ = options.tileZ;

          if (tileLevel >= 1) {
            return undefined;
          }

          const dimensions = this.dimensions;
          const voxelCount = dimensions.x * dimensions.y * dimensions.z;
          const type = this.types[0];
          const channelCount = Cesium.MetadataType.getComponentCount(type);
          const dataColor = new Float32Array(voxelCount * channelCount);

          const randomSeed =
            tileZ * dimensions.y * dimensions.x + tileY * dimensions.x + tileX;
          Cesium.Math.setRandomNumberSeed(randomSeed);
          const hue = Cesium.Math.nextRandomNumber();

          for (let z = 0; z < dimensions.z; z++) {
            for (let y = 0; y < dimensions.y; y++) {
              for (let x = 0; x < dimensions.x; x++) {
                const lerperX = x / (dimensions.x - 1);
                const lerperY = y / (dimensions.y - 1);
                const lerperZ = z / (dimensions.z - 1);

                const h = hue + lerperX * 0.5 - lerperY * 0.3 + lerperZ * 0.2;
                const s = 1.0 - lerperY * 0.2;
                const v = 0.5 + 2.0 * (lerperZ - 0.5) * 0.2;
                const color = Cesium.Color.fromHsl(h, s, v, 1.0, scratchColor);

                const index =
                  z * dimensions.y * dimensions.x + y * dimensions.x + x;

                dataColor[index * channelCount + 0] = color.red;
                dataColor[index * channelCount + 1] = color.green;
                dataColor[index * channelCount + 2] = color.blue;
                dataColor[index * channelCount + 3] = 0.75;
              }
            }
          }

          return Promise.resolve([dataColor]);
        };

        function ProceduralMultiTileVoxelProvider(shape) {
          this.shape = shape;
          this.minBounds = getMinBounds(shape);
          this.maxBounds = getMaxBounds(shape);
          this.dimensions = new Cesium.Cartesian3(4, 4, 4);
          this.paddingBefore = new Cesium.Cartesian3(1, 1, 1);
          this.paddingAfter = new Cesium.Cartesian3(1, 1, 1);
          this.names = ["color"];
          this.types = [Cesium.MetadataType.VEC4];
          this.componentTypes = [Cesium.MetadataComponentType.FLOAT32];

          this._levelCount = 2;
          this._allVoxelData = new Array(this._levelCount);

          const allVoxelData = this._allVoxelData;
          const levelCount = this._levelCount;
          const channelCount = Cesium.MetadataType.getComponentCount(
            this.types[0]
          );
          const voxelDimensions = this.dimensions;
          const voxelCountX = voxelDimensions.x;
          const voxelCountY = voxelDimensions.y;
          const voxelCountZ = voxelDimensions.z;

          for (let level = 0; level < levelCount; level++) {
            const dimAtLevel = Math.pow(2, level);
            const voxelCountLevelX = voxelCountX * dimAtLevel;
            const voxelCountLevelY = voxelCountY * dimAtLevel;
            const voxelCountLevelZ = voxelCountZ * dimAtLevel;
            const voxelsPerLevel =
              voxelCountLevelX * voxelCountLevelY * voxelCountLevelZ;
            const levelData = (allVoxelData[level] = new Array(
              voxelsPerLevel * channelCount
            ));

            for (let x = 0; x < voxelCountLevelX; x++) {
              for (let y = 0; y < voxelCountLevelY; y++) {
                for (let z = 0; z < voxelCountLevelZ; z++) {
                  const index =
                    z * voxelCountLevelY * voxelCountLevelX +
                    y * voxelCountLevelX +
                    x;
                  const lerperX = x / (voxelCountLevelX - 1);
                  const lerperY = y / (voxelCountLevelY - 1);
                  const lerperZ = z / (voxelCountLevelZ - 1);
                  const repeatX = 5;
                  const repeatY = 5;
                  const repeatZ = 1;
                  let xLocal = lerperX * repeatX;
                  let yLocal = lerperY * repeatY;
                  let zLocal = lerperZ * repeatZ;
                  xLocal = xLocal - Math.floor(xLocal);
                  yLocal = yLocal - Math.floor(yLocal);
                  zLocal = zLocal - Math.floor(zLocal);
                  const xDiff = xLocal - 0.5;
                  const yDiff = yLocal - 0.5;
                  const zDiff = zLocal - 0.5;

                  const dist = Math.sqrt(
                    xDiff * xDiff + yDiff * yDiff + zDiff * zDiff
                  );
                  const alpha = 1.0;

                  levelData[index * channelCount + 0] = lerperX;
                  levelData[index * channelCount + 1] = lerperY;
                  levelData[index * channelCount + 2] = lerperZ;
                  levelData[index * channelCount + 3] = alpha;
                }
              }
            }
          }
        }

        ProceduralMultiTileVoxelProvider.prototype.requestData = function (
          options
        ) {
          const tileLevel = options.tileLevel;
          const tileX = options.tileX;
          const tileY = options.tileY;
          const tileZ = options.tileZ;

          const levelCount = this._levelCount;
          if (tileLevel >= levelCount) {
            return undefined;
          }

          const type = this.types[0];
          const channelCount = Cesium.MetadataType.getComponentCount(type);
          const paddingBefore = this.paddingBefore;
          const paddingAfter = this.paddingAfter;
          const dimensions = this.dimensions;
          const dimensionsPadding = Cesium.Cartesian3.fromElements(
            dimensions.x + paddingBefore.x + paddingAfter.x,
            dimensions.y + paddingBefore.y + paddingAfter.y,
            dimensions.z + paddingBefore.z + paddingAfter.z
          );
          const dimAtLevel = Math.pow(2, tileLevel);
          const dimensionsGlobal = Cesium.Cartesian3.fromElements(
            dimensions.x * dimAtLevel,
            dimensions.y * dimAtLevel,
            dimensions.z * dimAtLevel
          );
          const minimumGlobalCoord = Cesium.Cartesian3.ZERO;
          const maximumGlobalCoord = new Cesium.Cartesian3(
            dimensionsGlobal.x - 1,
            dimensionsGlobal.y - 1,
            dimensionsGlobal.z - 1
          );
          let coordGlobal = new Cesium.Cartesian3();
          let coordTile = new Cesium.Cartesian3();

          const dataGlobal = this._allVoxelData;
          const dataTile = new Float32Array(
            dimensionsPadding.x *
              dimensionsPadding.y *
              dimensionsPadding.z *
              channelCount
          );

          for (let z = 0; z < dimensionsPadding.z; z++) {
            for (let y = 0; y < dimensionsPadding.y; y++) {
              for (let x = 0; x < dimensionsPadding.x; x++) {
                coordTile = Cesium.Cartesian3.fromElements(x, y, z, coordTile);

                const indexTile =
                  coordTile.z * dimensionsPadding.y * dimensionsPadding.x +
                  coordTile.y * dimensionsPadding.x +
                  coordTile.x;

                coordGlobal = Cesium.Cartesian3.clamp(
                  Cesium.Cartesian3.fromElements(
                    tileX * dimensions.x + (coordTile.x - paddingBefore.x),
                    tileY * dimensions.y + (coordTile.y - paddingBefore.y),
                    tileZ * dimensions.z + (coordTile.z - paddingBefore.z),
                    coordGlobal
                  ),
                  minimumGlobalCoord,
                  maximumGlobalCoord,
                  coordGlobal
                );

                const indexGlobal =
                  coordGlobal.z * dimensionsGlobal.y * dimensionsGlobal.x +
                  coordGlobal.y * dimensionsGlobal.x +
                  coordGlobal.x;

                for (let c = 0; c < channelCount; c++) {
                  dataTile[indexTile * channelCount + c] =
                    dataGlobal[tileLevel][indexGlobal * channelCount + c];
                }
              }
            }
          }
          return Promise.resolve([dataTile]);
        };

        function createPrimitive(provider, customShader, modelMatrix) {
          viewer.scene.primitives.removeAll();

          const voxelPrimitive = viewer.scene.primitives.add(
            new Cesium.VoxelPrimitive({
              provider: provider,
              customShader: customShader,
              modelMatrix: modelMatrix,
            })
          );

          viewer.voxelInspector.viewModel.voxelPrimitive = voxelPrimitive;
          viewer.camera.flyToBoundingSphere(voxelPrimitive.boundingSphere, {
            duration: 0.0,
          });

          return voxelPrimitive;
        }

        const customShaderColor = new Cesium.CustomShader({
          fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
  {
      material.diffuse = fsInput.metadata.color.rgb;
      material.alpha = 1.0;
  }`,
        });

        const customShaderWhite = new Cesium.CustomShader({
          fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
  {
      material.diffuse = vec3(1.0);
      material.alpha = 1.0;
  }`,
        });

        const modelMatrix = Cesium.Matrix4.fromScale(
          Cesium.Cartesian3.fromElements(
            Cesium.Ellipsoid.WGS84.maximumRadius,
            Cesium.Ellipsoid.WGS84.maximumRadius,
            Cesium.Ellipsoid.WGS84.maximumRadius
          )
        );

        Sandcastle.addToolbarMenu([
          {
            text: "Ellipsoid - Procedural Tile",
            onselect: function () {
              const provider = new ProceduralSingleTileVoxelProvider(
                Cesium.VoxelShapeType.ELLIPSOID
              );
              const primitive = createPrimitive(
                provider,
                customShaderColor,
                modelMatrix
              );
            },
          },
          {
            text: "Cylinder - Procedural Tile",
            onselect: function () {
              const provider = new ProceduralSingleTileVoxelProvider(
                Cesium.VoxelShapeType.CYLINDER
              );
              const primitive = createPrimitive(
                provider,
                customShaderColor,
                modelMatrix
              );
            },
          },
          {
            text: "Box - Procedural Tile",
            onselect: function () {
              const provider = new ProceduralSingleTileVoxelProvider(
                Cesium.VoxelShapeType.BOX
              );
              const primitive = createPrimitive(
                provider,
                customShaderColor,
                modelMatrix
              );
            },
          },
          {
            text: "Box - Procedural Tileset",
            onselect: function () {
              const provider = new ProceduralMultiTileVoxelProvider(
                Cesium.VoxelShapeType.BOX
              );
              const primitive = createPrimitive(
                provider,
                customShaderColor,
                modelMatrix
              );
            },
          },
          {
            text: "Box - 3D Tiles",
            onselect: async function () {
              const provider = await Cesium.Cesium3DTilesVoxelProvider.fromUrl(
                "../../SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json"
              );
              const primitive = createPrimitive(
                provider,
                customShaderWhite,
                modelMatrix
              );
            },
          },
          {
            text: "Ellipsoid - Procedural Tileset",
            onselect: function () {
              const provider = new ProceduralMultiTileVoxelProvider(
                Cesium.VoxelShapeType.ELLIPSOID
              );
              const primitive = createPrimitive(
                provider,
                customShaderColor,
                modelMatrix
              );
            },
          },
          {
            text: "Ellipsoid - 3D Tiles",
            onselect: async function () {
              const provider = await Cesium.Cesium3DTilesVoxelProvider.fromUrl(
                "../../SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json"
              );
              const primitive = createPrimitive(
                provider,
                customShaderWhite,
                Cesium.Matrix4.IDENTITY
              );
            },
          },
          {
            text: "Cylinder - Procedural Tileset",
            onselect: function () {
              const provider = new ProceduralMultiTileVoxelProvider(
                Cesium.VoxelShapeType.CYLINDER
              );
              const primitive = createPrimitive(
                provider,
                customShaderColor,
                modelMatrix
              );
            },
          },
          {
            text: "Cylinder - 3D Tiles",
            onselect: async function () {
              const provider = await Cesium.Cesium3DTilesVoxelProvider.fromUrl(
                "../../SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json"
              );
              const primitive = createPrimitive(
                provider,
                customShaderWhite,
                modelMatrix
              );
            },
          },
        ]);

        const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
        handler.setInputAction(function (movement) {
          const scene = viewer.scene;
          const camera = scene.camera;
          const mousePosition = movement.position;
          const pickedPrimitive = scene.pick(mousePosition);
          console.log(pickedPrimitive);
        }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
        //Sandcastle_End
      };
      if (typeof Cesium !== "undefined") {
        window.startupCalled = true;
        window.startup(Cesium).catch((error) => {
          "use strict";
          console.error(error);
        });
        Sandcastle.finishedLoading();
      }
    </script>
  </body>
</html>
